package photon;

import java.util.List;
import light.Light;
import math.Vecmath;
import prim.PRect;
import prim.Primitive;

public class PhotonMapper
{
    
    private static final int PHOTONS_PER_LIGHT = 5_000_000;
    
    private static final double MINIMUM_PHOTON_ENERGY = 0.001;
    
    private static final double DIR_NOISE = 0.0005;
    private static final double REFLECT_NOISE = 0.001;
    
    private static final int BLUR_STEPS = 6;
    
    
    private List<Primitive> listPrimitives;
    public void setPrimitives(List<Primitive> list) { this.listPrimitives = list; }
    
    private List<Light> listLights;
    public void setLights(List<Light> list) { this.listLights = list; }
    
    public PhotonMapper()
    {
        
    }
    
    public void tracePhotons()
    {
        for (Light light : this.listLights)
        {
            // Generate N photons for light
            int numPhotons0 = (int)(Math.sqrt(PhotonMapper.PHOTONS_PER_LIGHT) + 0.5);
            int numPhotons1 = numPhotons0;
            for (int i = 0; i < numPhotons0; i++)
            {
                for (int j = 0; j < numPhotons1; j++)
                {
                    // Generate photon origin
                    double[] photonOrigin = light.getOrigin();
                    
                    // Generate photon direction
                    double t0 = ((double) i) / numPhotons0;
                    double t1 = ((double) j) / numPhotons1;
                    double[] photonDir = light.getDirection(t0, t1);
                    photonDir[0] += PhotonMapper.DIR_NOISE * Vecmath.getRandIn(-1.0, 1.0);
                    photonDir[1] += PhotonMapper.DIR_NOISE * Vecmath.getRandIn(-1.0, 1.0);
                    photonDir[2] += PhotonMapper.DIR_NOISE * Vecmath.getRandIn(-1.0, 1.0);
                    
                    // Generate photon color
                    double[] photonCol = light.getColor();
                    
                    // Initiate the photon
                    Photon photon = new Photon(
                        photonCol, 
                        photonOrigin, 
                        photonDir
                    );
                    
                    // Trace the photon through space
                    this.tracePhoton(photon);
                }
            }
        }
    }
    
    public void blurColorMaps()
    {
        for (Primitive prim : this.listPrimitives)
        {
//            for (int blurStep = PhotonMapper.BLUR_STEPS; blurStep > 0; blurStep--)
            for (int blurStep = 1; blurStep <= PhotonMapper.BLUR_STEPS; blurStep++)
            {
                ((PRect) prim).getColorMap().blendDataBilinear(blurStep);
            }
        }
    }
    
    private void tracePhoton(Photon photon)
    {
        // Find the nearest intersecting primitive
        double intersectT = Double.MAX_VALUE;
        Primitive intersectPrim = null;
        for (Primitive primitive : this.listPrimitives)
        {
            // Store the next lowest intersecting info
            double t = primitive.getIntersectT(photon);
            if (t < intersectT && t >= 0.0)
            {
                intersectT = t;
                intersectPrim = primitive;
            }
        }
        
        // If intersectPrim is null, then no intersection occurred
        if (intersectPrim == null)
        {
            return;
        }
        
        // Save intersect point
        double[] intersectPoint = photon.getPointAt(intersectT);
        
        // Transfer light energy to the intersected primitive
        intersectPrim.absorbPhoton(
            photon, 
            intersectPoint
        );
        
        // If photon still has juice, reflect and trace again
        if (photon.getEnergy() >= PhotonMapper.MINIMUM_PHOTON_ENERGY)
        {
            double[] intersectNormal = intersectPrim.getNormal(intersectPoint);
            
            // Only reflect if PRect is facing the correct way
            if (Vecmath.dotProd(intersectNormal, intersectPoint) > 0.0)
            {
                double[] reflect = Vecmath.reflect(
                    photon.getDirection(), 
                    intersectNormal
                );
                reflect[0] += PhotonMapper.REFLECT_NOISE * Vecmath.getRandIn(-1.0, 1.0);
                reflect[1] += PhotonMapper.REFLECT_NOISE * Vecmath.getRandIn(-1.0, 1.0);
                reflect[2] += PhotonMapper.REFLECT_NOISE * Vecmath.getRandIn(-1.0, 1.0);
                Vecmath.normalize(reflect);
                
                double[] offset = Vecmath.getCopyOf(reflect);
                Vecmath.scale(offset, Vecmath.COMPARISON_ERROR);
                Vecmath.translate(offset, intersectPoint);

                Photon reflectPhoton = new Photon(
                    photon.getColor(), 
                    offset, 
                    reflect
                );

                this.tracePhoton(reflectPhoton);
            }
        }
        
    }
    
}
